/* verify.c -- verify the validity of each neighbor periodically
 *
 *    Copyright 2010 Patrick Allen
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
*/

#include <time.h>
#include <stdlib.h>
#include <signal.h>

#include "advrp.h"
#include "debug.h"
#include "receiver.h"
#include "holddown.h"

_Bool haveNeighbor = 0;
pthread_cond_t condNeighbor;
extern pthread_mutex_t mutexAdvrp;
extern pthread_mutex_t mutexHaveNeighbor;
extern INVALID_TIMER;
extern neighbor * firstNeighbor, * lastNeighbor, * firstRemoteNeighbor;
extern pthread_cond_t condCreate;
extern pthread_mutex_t mutexCreate;

time_t check_neighbors(void);
void neighbor_del(neighbor * dead);

void * verify_loop(void * notused)
{
	time_t sleeptime = INVALID_TIMER;
	sigset_t newmask;

	/* Drop effective privs */
	drop_privs(1);

	/* Ignore all signals */
	newmask.__val[0] = SIGINT;
	newmask.__val[1] = SIGTERM;
	newmask.__val[2] = SIGHUP;
	pthread_sigmask(SIG_SETMASK, &newmask, 0);

	DBG(DEV_1, "Verify thread id: %u", pthread_self());

	/* Tell main to continue creating threads */
	pthread_mutex_lock(&mutexCreate);
	pthread_cond_signal(&condCreate);
	pthread_mutex_unlock(&mutexCreate);

	while (1) {
		pthread_mutex_lock(&mutexHaveNeighbor);
		DBG(VRFY_1, "Checking neighbor status.");
		if (! haveNeighbor) {
			/* Don't have a neighbor, wait until we do */
			DBG(VRFY_1, "No neighbors, waiting...");
			pthread_mutex_unlock(&mutexHaveNeighbor);
			pthread_cond_wait(&condNeighbor, &mutexAdvrp);
			pthread_mutex_unlock(&mutexAdvrp);
		} else {
			pthread_mutex_unlock(&mutexHaveNeighbor);
		}

		/* Have at least one client, start making sure they're valid */
		DBG(VRFY_1, "Found a neighbor.");
		pthread_mutex_lock(&mutexAdvrp);
		sleeptime = check_neighbors();
		pthread_mutex_unlock(&mutexAdvrp);
		DBG(VRFY_2, "Sleeping for %u seconds.", sleeptime);
		sleep(sleeptime);
	}
}

time_t check_neighbors(void)
{
	neighbor * curr;
	time_t nextchk = INVALID_TIMER + 1; /* how long till the next check */
	time_t ctime = time(NULL);          /* current time */
	time_t age;                         /* how long ago neighbor was seen */

	for (curr = firstRemoteNeighbor; curr != NULL; curr = curr->next) {
		if ((age = ctime - curr->lastheard) > INVALID_TIMER) {

			/* Neighbor is invalid */
			neighbor_del(curr);
		} else if (INVALID_TIMER - age < nextchk) {
			nextchk = INVALID_TIMER - age; 
		}
	}

	/* Wait at least one second before the next check.
	 * Also, if we check at nextchk rather than nextchk + 1, any lost
	 * neighbors will end up as valid for another second, so we
	 * end up sleeping again for one second anyway.
	 */
	return nextchk < 1 ? 1 : nextchk + 1;
}

/* neighbor_del
 * Remove a neighbor from the list of neighbors. Remove their routes and put
 * them on holddown.
 */
void neighbor_del(neighbor * dead)
{
	neighborRoute * route;

	DBG(VRFY_1, "Deleting invalid neighbor: %s", dead->ipstr);

	if (dead == firstRemoteNeighbor) {
		DBG(DEV_2, "Deleting first neighbor in the list.");
		firstRemoteNeighbor = dead->next;
	} else {
		DBG(DEV_2, "Deleting non-first neighbor in the list.");
	}

	if (dead == lastNeighbor) {
		DBG(DEV_2, "This was lastNeighbor.");
		lastNeighbor = dead->prev;
	} else {
		DBG(DEV_2, "This was NOT lastNeighbor.");
	}

	if (dead->prev)
		dead->prev->next = dead->next;
	if (dead->next)
		dead->next->prev = dead->prev;

	if (firstRemoteNeighbor == NULL) {
		pthread_mutex_lock(&mutexHaveNeighbor);
		haveNeighbor--;
		pthread_mutex_unlock(&mutexHaveNeighbor);
		DBG(VRFY_2, "Last neighbor is being deleted.");
	}

	/* Delete the neighbor's routes. They will all be placed on holddown,
	 * meaning we don't need to find successor routes to these prefixes.
	 *
	 * The neighbor_route_del function will replace firstroute with the
	 * "new" first route every time, so we shouldn't try to walk through the
	 * list; just keep deleting the first route until it is NULL.
	 */
	while (dead->firstroute) {
		holddown_add(dead->firstroute->hash);
		DBG(DEV_2, "Calling neighbor_route_del.");
		neighbor_route_del(dead, dead->firstroute);
	}

	/* Finally, free the neighbor's memory */
	DBG(DEV_2, "Freeing neighbor memory.");
	free(dead);
	return;
}
